/*
 * Copyright (c) 2005 - 2010 CAS Dev Team
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the CAS Dev. Team nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      ASSQLConnector.cpp
 *
 * $CAS$
 */

#include <ASSQLConnector.hpp>

#include <ASDebugHelper.hpp>
#include <ASPool.hpp>
#include <ASLogCodes.hpp>
#include <ASLogger.hpp>

#include <ASSQLConnectorObject.hpp>

using namespace CAS;

#define C_GLOBAL_RESOURCE      0x00000001
#define C_SERVER_RESOURCE      0x00000002

#define C_CHECK_CONNECTION     0x00000001
#define C_NO_CHECK_CONNECTION  0x00000002

namespace CAS_MOD
{

//
// A constructor
//
ASSQLConnector::ASSQLConnector()
{
	;;
}

//
// Initialize module
//
INT_32 ASSQLConnector::InitModule(CTPP::CDT      & oConfiguration,
                                  CAS::ASPool    & oModulesPool,
                                  CAS::ASPool    & oObjectPool,
                                  CAS::ASLogger  & oLogger)
{
	return 0;
}

//
// Initialize server hook
//
INT_32 ASSQLConnector::InitServer(CTPP::CDT      & oConfiguration,
                                  CAS::ASPool    & oGlobalPool,
                                  CAS::ASPool    & oServerPool,
                                  CAS::ASLogger  & oLogger)
{

	using namespace CTPP;
	DEBUG_HELPER(&oLogger, "ASSQLConnector::InitServer");

	CDT oTMP = oConfiguration.GetCDT("Connections");
	CDT::eValType eType = oTMP.GetType();

	if (eType != CDT::HASH_VAL &&
	    eType != CDT::ARRAY_VAL)
	{
		DEBUG_HELPER_MESSAGE("Cannot find any \"Connections\" configuration section");

		oLogger.Alert("Cannot find any \"Connections\" configuration section");
		return -1;
	}

	oTMP = oTMP.GetCDT("Connection");
	eType = oTMP.GetType();

	// Only one connection
	if (eType == CDT::HASH_VAL)
	{
		// Create and register object
		return CreateAndRegisterObject(oTMP, oGlobalPool, oServerPool, oLogger);
	}
	else if (eType == CDT::ARRAY_VAL)
	{
		for(UINT_32 iI = 0; iI < oTMP.Size(); ++iI)
		{
			if (CreateAndRegisterObject(oTMP[iI], oGlobalPool, oServerPool, oLogger) == -1) { return -1; }
		}
	}
	else
	{
		STLW::string sError("Invalid type of \"Connection\" configuration section: \"");
		sError += oTMP.PrintableType(); sError += "\"";
		DEBUG_HELPER_MESSAGE(sError);

		oLogger.Alert(sError.c_str());
		return -1;
	}

	DEBUG_HELPER_MESSAGE("All done with SQL connector");

return 0;
}

//
// Get object type
//
CCHAR_P ASSQLConnector::GetModuleType() const { return "ASSQLConnector"; }

//
// Get module name
//
CCHAR_P ASSQLConnector::GetObjectName() const { return "ASSQLConnector"; }

//
// Create and register new SQL connection
//
INT_32 ASSQLConnector::CreateAndRegisterObject(CTPP::CDT      & oSQL,
                                               CAS::ASPool    & oGlobalPool,
                                               CAS::ASPool    & oServerPool,
                                               CAS::ASLogger  & oLogger)
{
	using namespace CTPP;
	DEBUG_HELPER(&oLogger, "ASSQLConnector::CreateAndRegisterObject");

	ASSQLConnectorObject * pSQLConnectorObject = NULL;
	STLW::string sName;

	try
	{
		// Connection name
		sName = oSQL.GetCDT("Name").GetString();

		// Connection type: global or server
		const STLW::string sType = oSQL.GetCDT("Type").GetString();
		INT_32 iResourceType = 0;

		if      (strcasecmp("Global", sType.c_str()) == 0) { iResourceType = C_GLOBAL_RESOURCE; }
		else if (strcasecmp("Server", sType.c_str()) == 0) { iResourceType = C_SERVER_RESOURCE; }
		else
		{
			oLogger.Alert("Invalid pool type `%s` for SQL connector `%s`", sType.c_str(), sName.c_str());
			return -1;
		}

		// Check existence of another connection
		STLW::string sTMP = oSQL.GetCDT("CheckConnection").GetString();
		INT_32 iCheckExistedConnection;
		if (strcasecmp("yes", sTMP.c_str()) == 0) { iCheckExistedConnection = C_CHECK_CONNECTION;    }
		else                                      { iCheckExistedConnection = C_NO_CHECK_CONNECTION; }

		// Create connectiom
		pSQLConnectorObject = new ASSQLConnectorObject();

		// Get list of memcached servers
		const CDT oDatabase = oSQL.GetCDT("Database");
		if (oDatabase.GetType() == CDT::HASH_VAL)
		{
			const STLW::string sDriver     = oDatabase.GetCDT("Driver").GetString();
			const STLW::string sConnString = oDatabase.GetCDT("ConnString").GetString();

			pSQLConnectorObject -> AddConnection(sDriver, sConnString);
			oLogger.Debug("AddServer: %s/%s", sDriver.c_str(), sConnString.c_str());
		}
		else if (oDatabase.GetType() == CDT::ARRAY_VAL)
		{
			for (UINT_32 iPos = 0; iPos < oDatabase.Size(); ++iPos)
			{
				const CDT oDBRec = oDatabase.GetCDT(iPos);
				if (oDBRec.GetType() != CDT::HASH_VAL)
				{
					oLogger.Alert("Invalid type (`%s`) of record `<Database .... />` for SQL connector `%s`", oDBRec.PrintableType(), sName.c_str());
					return -1;
				}

				const STLW::string sDriver     = oDBRec.GetCDT("Driver").GetString();
				const STLW::string sConnString = oDBRec.GetCDT("ConnString").GetString();

				pSQLConnectorObject -> AddConnection(sDriver, sConnString);
				oLogger.Debug("AddServer: %s/%s", sDriver.c_str(), sConnString.c_str());
			}
		}
		else if (oDatabase.GetType() == CDT::UNDEF)
		{
			oLogger.Alert("Need at least one record `<Database .... />` for SQL connector `%s`", sName.c_str());
			return -1;
		}
		else
		{
			oLogger.Alert("Invalid type (`%s`) of record `<Database .... />` for SQL connector `%s`", oDatabase.PrintableType(), sName.c_str());
			return -1;
		}

		if (iResourceType == C_GLOBAL_RESOURCE)
		{
			// Check object
			if (oGlobalPool.CheckResourceByName<ASObject>(sName) == 0)
			{
				delete pSQLConnectorObject;

				STLW::string sError("SQL connection object \"");
				sError += sName + " already present in GLOBAL pool";
				DEBUG_HELPER_MESSAGE(sError);

				// Fatal Error
				if (iCheckExistedConnection == C_CHECK_CONNECTION)
				{
					oLogger.Alert(sError.c_str());
					return -1;
				}

				oLogger.Warn(sError.c_str());
				// Only print warning
				return 0;
			}

			// Ok, add connection
			oGlobalPool.RegisterResource<ASObject>(pSQLConnectorObject, sName);
			// Remember to clear
			vObjects.push_back(pSQLConnectorObject);

			DEBUG_HELPER_MESSAGE(STLW::string("SQL connection connector \"") + sName + "\" registered in GLOBAL pool");
		}
		// C_SERVER_RESOURCE
		else
		{
			// Check object
			if (oServerPool.CheckResourceByName<ASObject>(sName) == 0)
			{
				delete pSQLConnectorObject;

				STLW::string sError("SQL connection object \"");
				sError += sName + " already present in SERVER pool";
				DEBUG_HELPER_MESSAGE(sError);

				// Fatal Error
				if (iCheckExistedConnection == C_CHECK_CONNECTION)
				{
					oLogger.Alert(sError.c_str());
					return -1;
				}

				oLogger.Warn(sError.c_str());
				// Only print warning
				return 0;
			}

			// Ok, add connection
			oServerPool.RegisterResource<ASObject>(pSQLConnectorObject, sName);
			// Remember to clear
			vObjects.push_back(pSQLConnectorObject);

 			DEBUG_HELPER_MESSAGE(STLW::string("SQL connection connector \"") + sName + "\" registered in SERVER pool");
		}
	}
	catch(STLW::exception &e)
	{
		if (pSQLConnectorObject != NULL) { delete pSQLConnectorObject; }
		STLW::string sError("Cannot create & register \"");
		sError += sName + "\" connection object in pool: " + e.what();

		oLogger.Alert(sError.c_str());
		return -1;
	}

return 0;
}

//
// A destructor
//
ASSQLConnector::~ASSQLConnector() throw()
{
	STLW::vector<ASObject *>::iterator itvObjects = vObjects.begin();
	while (itvObjects != vObjects.end())
	{
		delete *itvObjects;
		++itvObjects;
	}
}

} // namespace CAS_MOD
// End.
